說到儲存資料,大家第一個會想到的,應該都是 LocalStorage 或 SessionStorage 吧。
但如果今天要處理更複雜的資料查詢,或是需要管理更多的結構化數據時,LocalStorage 可能不太方便,這時候,也許可以考慮使用 IndexedDB API。我們可以在使用者的瀏覽器中,透過 IndexedDB API 建立、讀取和管理大量的結構化數據,處理類數據庫的操作,且數據量也不受限,就來看看 IndexedDB API 該怎麼使用吧!
以 Chrome 瀏覽器為例,開啟開發者工具後,進入 Application (應用程式) 的面板,在左側找到 IndexedDB 後將他開啟,就能看到應用程式中所有的 IndexedDB 數據庫,並可以檢視各個資料表的內容。
我們先試著建立一個資料庫吧!IndexedDB 是透過版號來管理資料庫的,每當我們需要對資料庫進行更動時(例如增加新的資料表或建立索引),就需要更新版號。
使用 indexedDB.open()
建立一個 MyDatabase
的資料庫,然後再建立一張 users
表,這個表有兩個 index:name
和 email
。我將 email
設為 { unique: true }
,表示唯一值,不允許重複。
let db;
const request = indexedDB.open('MyDatabase', 1);
request.onupgradeneeded = function(event) {
db = event.target.result;
const objectStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('email', 'email', { unique: true });
};
request.onsuccess = function(event) {
db = event.target.result;
console.log('資料庫建立成功');
};
request.onerror = function(event) {
console.error('資料庫建立失敗', event);
};
切到 IndexedDB 可以看到剛剛建立的 MyDatabase
,裡面有一個 users
表
建立資料庫之後,一定要來走一遍 CRUD (Create / Read / Update / Delete),範例會做四個按鈕,分別代表這四個動作。
<button onclick="createUser()">新增使用者</button>
<button onclick="readUser()">載入使用者資料</button>
<button onclick="updateUser()">更新使用者資料</button>
<button onclick="deleteUser()">刪除使用者</button>
function createUser() {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
const user = { name: 'MUKI', email: 'muki@tw.com' };
const request = objectStore.add(user);
request.onsuccess = function() {
console.log('新增成功');
};
request.onerror = function() {
console.error('新增失敗');
};
}
IndexedDB API 使用 db.transaction
來建立一個資料庫交易,你也可能聽過資料庫事務,他們都是指 database 的transaction。
什麼是資料庫交易呢?簡單來說,就是在執行資料庫指令時的一個方法,他的概念就是交易,要嘛全部交易成功、要嘛全部交易失敗。舉例來說,假設小明跟小華雙方在做買賣,小明突然反悔不賣東西給小華了,小華即使想付錢也拿不到東西,這就是交易失敗,小明跟小華不會做任何事情、也不會產生任何改變。
那麼回到 IndexedDB API,db.transaction
包含多個操作,如新增、刪除... 等,而使用 transactoin
就會保證這些操作要嘛全部成功、要嘛全部失敗,如此一來就能保證資料的完整性與一致性。
const transaction = db.transaction(storeNames, mode);
db.transaction
有兩個參數如下:
storeNames
:可以接收字串或是字串陣列,如果我們只操作一個物件,你可以用字串 db.transaction('users', 'readwrite')
,如果操作多個物件,可以像我一樣用字串陣列 db.transaction(['users'], 'readwrite')
mode
:有兩個值可用
readonly
:只能讀,不能寫readwrite
:可以讀也可以寫我們來拆解一下範例「新增使用者」的程式碼
// 建立一個新的交易
const transaction = db.transaction(['users'], 'readwrite');
// 取得要操作的儲存對象,我們在最開始建立資料庫時,已經有用 db.createObjectStore 建立 'users'
const objectStore = transaction.objectStore('users');
const user = { name: 'MUKI', email: 'muki@tw.com' };
// 增加 user 資料
const request = objectStore.add(user);
// 取得與處理對應的結果
request.onsuccess = function() {
console.log('新增成功');
};
request.onerror = function() {
console.error('新增失敗');
};
有接觸或研究過資料庫的朋友,應該對他的特性 ACID 不陌生,這邊就班門弄斧簡單分享這四個特性:
接下來的查詢、更新以及刪除,也都會使用 db.transaction
做處理。
function getUserByEmail(email) {
// 因為只要查詢使用者,所以 mode 參數可以設定為 readonly。
const transaction = db.transaction(['users'], 'readonly);
const objectStore = transaction.objectStore('users');
// 設定索引的欄位
const index = objectStore.index('email');
const request = index.get(email);
request.onsuccess = function() {
if (request.result) {
console.log('找到使用者:', request.result);
} else {
console.log('該使用者不存在');
}
};
request.onerror = function(event) {
console.error('搜尋失敗', event);
};
}
getUserByEmail('muki@tw.com');
上面的例子是針對 email 欄位做搜尋,我們也可以用 getAll()
直接讀取所有使用者的資料:
const request = objectStore.getAll();
使用 objectStore.get()
取得要更新的使用者資料,如果沒有像上一個範例一樣特別設定 index
(const index = objectStore.index('email')
),那傳入的參數預設會是使用者 ID
function updateUser() {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
// 取得 ID = 1 的使用者資料
const request = objectStore.get(1);
request.onsuccess = function() {
const user = request.result;
user.name = 'MMM';
};
request.onerror = function() {
console.error('讀取失敗');
};
}
使用 objectStore.delete()
刪除使用者資料
function deleteUser() {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
// 刪除 ID = 1 的使用者資料
const request = objectStore.delete(1);
request.onsuccess = function() {
console.log('刪除成功');
};
request.onerror = function() {
console.error('刪除失敗');
};
}
這邊提供上述的範例程式碼頁面:https://mukiwu.github.io/web-api-demo/indexeddb.html
頁面有做一些調整,我加入了簡易的表單輸入,這樣大家會看得更清楚。
透過 IndexedDB API,我們可以在瀏覽器處理大量的結構化資料,可以做更多複雜的應用,下一篇會再跟大家分享幾個常見的應用,有任何問題,也歡迎留言討論。